using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Threading;
using System.Windows.Threading;

namespace MMXSaveGameConverter
{
    internal sealed class SaveGameViewItems : ObservableCollection<SaveGameViewItem>, IDisposable
    {
        private readonly DisposableStack _disposables = new DisposableStack();
        private readonly Dispatcher _dispatcher;

        public SaveGameViewItems(Dispatcher dispatcher)
        {
            try
            {
                _dispatcher = dispatcher;
                Initialize();
            }
            catch
            {
                Dispose();
                throw;
            }
        }

        public void Dispose()
        {
            _disposables.Dispose();
        }

        private void Initialize()
        {
            DirectoriesSet dirs = GetDirectories();
            foreach (string dir in dirs)
                GetSaves(dir);
        }

        private DirectoriesSet GetDirectories()
        {
            DirectoriesSet directories = new DirectoriesSet();

            try
            {
                string directory = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), "Ubisoft", "Ubisoft Game Launcher", "savegames");
                if (Directory.Exists(directory))
                {
                    foreach (string dir in Directory.GetDirectories(directory))
                    {
                        directories.Add(Path.Combine(dir, "401"));
                        directories.Add(Path.Combine(dir, "403"));
                    }
                }
            }
            catch (Exception ex)
            {
                Console.Error.WriteLine(ex);
            }

            directories.Add(() => Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "Orbit", "401", "RLD!"));
            directories.Add(() => Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "Orbit", "403", "RLD!"));
            directories.Add(() => Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "MightAndMagicXLegacy"));

            return directories;
        }

        private sealed class DirectoriesSet : IEnumerable<string>
        {
            private readonly HashSet<string> _set = new HashSet<string>();

            public void Add(string path)
            {
                if (string.IsNullOrEmpty(path) || !Directory.Exists(path))
                    return;

                _set.Add(path);
            }

            public void Add(Func<string> func)
            {
                try
                {
                    Add(func());
                }
                catch (Exception ex)
                {
                    Console.Error.WriteLine(ex);
                }
            }

            public IEnumerator<string> GetEnumerator()
            {
                return _set.GetEnumerator();
            }

            IEnumerator IEnumerable.GetEnumerator()
            {
                return GetEnumerator();
            }
        }

        public void GetSaves(string dir)
        {
            try
            {
                string[] files = Directory.GetFiles(dir);
                StartWatcher(dir);
                foreach (string file in files)
                {
                    SaveGameFormat? format = GetSaveFormat(file);
                    if (format != null)
                        GetSave(file, format.Value);
                }
            }
            catch (Exception ex)
            {
                Console.Error.WriteLine(ex);
            }
        }

        public void TryAdd(string[] files)
        {
            try
            {
                foreach (string file in files)
                    OnFileCreated(this, new FileSystemEventArgs(WatcherChangeTypes.Created, Path.GetDirectoryName(file), Path.GetFileName(file)));
            }
            catch (Exception ex)
            {
                Console.Error.WriteLine(ex);
            }
        }

        private void GetSave(string fullPath, SaveGameFormat value)
        {
            try
            {
                if (!File.Exists(fullPath))
                    throw new FileNotFoundException(fullPath);

                SaveGameViewItem item = new SaveGameViewItem
                {
                    FullPath = fullPath,
                    Directory = Path.GetDirectoryName(fullPath),
                    File = Path.GetFileNameWithoutExtension(fullPath),
                    Format = value
                };

                switch (value)
                {
                    case SaveGameFormat.Xml:
                        FromXml(item);
                        break;
                    case SaveGameFormat.Lgs:
                        FromLgs(item);
                        break;
                    case SaveGameFormat.Sav:
                        FromSav(item);
                        break;
                    case SaveGameFormat.Save:
                        FromSave(item);
                        break;
                    default:
                        throw new ArgumentOutOfRangeException("value");
                }

                Add(item);
            }
            catch (Exception ex)
            {
                Console.Error.WriteLine(ex);
            }
        }

        private void FromXml(SaveGameViewItem item)
        {
            item.Reader = new XmlFileReader(item.FullPath);
            item.Reader.SetMeta(item);
        }

        private void FromLgs(SaveGameViewItem item)
        {
            item.Reader = new LgsFileReader(item.FullPath);
            item.Reader.SetMeta(item);
        }

        private void FromSav(SaveGameViewItem item)
        {
            item.Reader = new SavFileReader(item.FullPath);
            item.Reader.SetMeta(item);
        }

        private void FromSave(SaveGameViewItem item)
        {
            item.Reader = new SaveFileReader(item.FullPath);
            item.Reader.SetMeta(item);
        }

        #region Watcher

        private void StartWatcher(string dir)
        {
            try
            {
                FileSystemWatcher watcher = _disposables.Add(new FileSystemWatcher(dir));
                watcher.Created += OnFileCreated;
                watcher.Deleted += OnFileDeleted;
                watcher.Renamed += OnFileRenamed;
                watcher.EnableRaisingEvents = true;
            }
            catch (Exception ex)
            {
                Console.Error.WriteLine(ex);
            }
        }

        private void OnFileCreated(object sender, FileSystemEventArgs e)
        {
            _dispatcher.Invoke(() =>
            {
                try
                {
                    SaveGameFormat? format = GetSaveFormat(e.FullPath);
                    if (format == null)
                        return;

                    for (int i = 0; i < 5; i++)
                    {
                        Thread.Sleep(2000);
                        try
                        {
                            using (FileStream fs = File.Open(e.FullPath, FileMode.Open, FileAccess.ReadWrite, FileShare.None))
                            {
                                // Checking locks
                            }

                            GetSave(e.FullPath, format.Value);

                            return;
                        }
                        catch (IOException)
                        {
                            // Waiting
                        }
                    }
                }
                catch (Exception ex)
                {
                    Console.Error.WriteLine(ex);
                }
            });
        }

        private void OnFileDeleted(object sender, FileSystemEventArgs e)
        {
            _dispatcher.Invoke(() =>
            {
                try
                {
                    SaveGameFormat? format = GetSaveFormat(e.FullPath);
                    if (format == null)
                        return;

                    SaveGameViewItem item = Items.FirstOrDefault(view => String.Equals(view.FullPath, e.FullPath, StringComparison.InvariantCultureIgnoreCase));
                    if (item == null)
                        return;

                    Remove(item);
                }
                catch (Exception ex)
                {
                    Console.Error.WriteLine(ex);
                }
            });
        }

        private void OnFileRenamed(object sender, RenamedEventArgs e)
        {
            _dispatcher.Invoke(() =>
            {
                try
                {
                    SaveGameFormat? format = GetSaveFormat(e.OldFullPath);
                    if (format == null)
                        return;

                    OnFileDeleted(sender, new FileSystemEventArgs(WatcherChangeTypes.Deleted, Path.GetDirectoryName(e.OldFullPath) ?? string.Empty, e.OldName));
                    OnFileCreated(sender, new FileSystemEventArgs(WatcherChangeTypes.Created, Path.GetDirectoryName(e.FullPath) ?? string.Empty, e.Name));
                }
                catch (Exception ex)
                {
                    Console.Error.WriteLine(ex);
                }
            });
        }

        #endregion

        private static SaveGameFormat? GetSaveFormat(string file)
        {
            string extension = (Path.GetExtension(file) ?? string.Empty).ToLower();
            switch (extension)
            {
                case ".lgs":
                    return SaveGameFormat.Lgs;
                case ".sav":
                    return SaveGameFormat.Sav;
                case ".save":
                    return SaveGameFormat.Save;
                case ".xml":
                    return SaveGameFormat.Xml;
            }
            return null;
        }
    }
}